Completed
Branch v7.x (4820d8)
by Rafael S.
02:23
created

T_CONST ➔ ???   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
nc 2
dl 0
loc 1
rs 10
cc 2
nop 1
1
/*
2
 * wavefile: Read and write wave files.
3
 * https://github.com/rochars/wavefile
4
 *
5
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining
8
 * a copy of this software and associated documentation files (the
9
 * "Software"), to deal in the Software without restriction, including
10
 * without limitation the rights to use, copy, modify, merge, publish,
11
 * distribute, sublicense, and/or sell copies of the Software, and to
12
 * permit persons to whom the Software is furnished to do so, subject to
13
 * the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be
16
 * included in all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
 *
26
 */
27
28
/**
29
 * @fileoverview The WaveFile class.
30
 */
31
32
/** @module wavefile */
33
34
import {bitdepth} from 'bitdepth';
35
import {riffIndex} from 'riff-chunks';
36
import {decode as decodeADPCM, encode as encodeADPCM} from 'imaadpcm';
37
import {alawmulaw} from 'alawmulaw';
38
import {encode, decode} from 'base64-arraybuffer';
0 ignored issues
show
introduced by
The variable decode already seems to be declared on line 36. Are you sure you want to import using decode as name?
Loading history...
introduced by
The variable encode already seems to be declared on line 36. Are you sure you want to import using encode as name?
Loading history...
39
import {pack, packArray, unpackFrom, unpackArrayFrom, types} from 'byte-data';
40
41
/**
42
 * @type {!Object}
43
 * @private
44
 */
45
let uInt16_ = {'bits': 16};
46
/**
47
 * @type {!Object}
48
 * @private
49
 */
50
let uInt32_ = {'bits': 32};
51
52
/**
53
 * Class representing a wav file.
54
 */
55
export class WaveFile {
56
57
  /**
58
   * @param {?Uint8Array} bytes A wave file buffer.
59
   * @throws {Error} If no 'RIFF' chunk is found.
60
   * @throws {Error} If no 'fmt ' chunk is found.
61
   * @throws {Error} If no 'data' chunk is found.
62
   */
63
  constructor(bytes=null) {
64
    /**
65
     * The container identifier.
66
     * 'RIFF', 'RIFX' and 'RF64' are supported.
67
     * @type {string}
68
     * @export
69
     */
70
    this.container = '';
71
    /**
72
     * @type {number}
73
     * @export
74
     */
75
    this.chunkSize = 0;
76
    /**
77
     * The format.
78
     * Always 'WAVE'.
79
     * @type {string}
80
     * @export
81
     */
82
    this.format = '';
83
    /**
84
     * The data of the 'fmt' chunk.
85
     * @type {!Object<string, *>}
86
     * @export
87
     */
88
    this.fmt = {
89
      /** @export @type {string} */
90
      'chunkId': '',
91
      /** @export @type {number} */
92
      'chunkSize': 0,
93
      /** @export @type {number} */
94
      'audioFormat': 0,
95
      /** @export @type {number} */
96
      'numChannels': 0,
97
      /** @export @type {number} */
98
      'sampleRate': 0,
99
      /** @export @type {number} */
100
      'byteRate': 0,
101
      /** @export @type {number} */
102
      'blockAlign': 0,
103
      /** @export @type {number} */
104
      'bitsPerSample': 0,
105
      /** @export @type {number} */
106
      'cbSize': 0,
107
      /** @export @type {number} */
108
      'validBitsPerSample': 0,
109
      /** @export @type {number} */
110
      'dwChannelMask': 0,
111
      /**
112
       * 4 32-bit values representing a 128-bit ID
113
       * @export @type {!Array<number>}
114
       */
115
      'subformat': []
116
    };
117
    /**
118
     * The data of the 'fact' chunk.
119
     * @type {!Object<string, *>}
120
     * @export
121
     */
122
    this.fact = {
123
      /** @export @type {string} */
124
      'chunkId': '',
125
      /** @export @type {number} */
126
      'chunkSize': 0,
127
      /** @export @type {number} */
128
      'dwSampleLength': 0
129
    };
130
    /**
131
     * The data of the 'cue ' chunk.
132
     * @type {!Object<string, *>}
133
     * @export
134
     */
135
    this.cue = {
136
      /** @export @type {string} */
137
      'chunkId': '',
138
      /** @export @type {number} */
139
      'chunkSize': 0,
140
      /** @export @type {number} */
141
      'dwCuePoints': 0,
142
      /** @export @type {!Array<!Object>} */
143
      'points': [],
144
    };
145
    /**
146
     * The data of the 'smpl' chunk.
147
     * @type {!Object<string, *>}
148
     * @export
149
     */
150
    this.smpl = {
151
      /** @export @type {string} */
152
      'chunkId': '',
153
      /** @export @type {number} */
154
      'chunkSize': 0,
155
      /** @export @type {number} */
156
      'dwManufacturer': 0,
157
      /** @export @type {number} */
158
      'dwProduct': 0,
159
      /** @export @type {number} */
160
      'dwSamplePeriod': 0,
161
      /** @export @type {number} */
162
      'dwMIDIUnityNote': 0,
163
      /** @export @type {number} */
164
      'dwMIDIPitchFraction': 0,
165
      /** @export @type {number} */
166
      'dwSMPTEFormat': 0,
167
      /** @export @type {number} */
168
      'dwSMPTEOffset': 0,
169
      /** @export @type {number} */
170
      'dwNumSampleLoops': 0,
171
      /** @export @type {number} */
172
      'dwSamplerData': 0,
173
      /** @export @type {!Array<!Object>} */
174
      'loops': [],
175
    };
176
    /**
177
     * The data of the 'bext' chunk.
178
     * @type {!Object<string, *>}
179
     * @export
180
     */
181
    this.bext = {
182
      /** @export @type {string} */
183
      'chunkId': '',
184
      /** @export @type {number} */
185
      'chunkSize': 0,
186
      /** @export @type {string} */
187
      'description': '', //256
188
      /** @export @type {string} */
189
      'originator': '', //32
190
      /** @export @type {string} */
191
      'originatorReference': '', //32
192
      /** @export @type {string} */
193
      'originationDate': '', //10
194
      /** @export @type {string} */
195
      'originationTime': '', //8
196
      /**
197
       * 2 32-bit values, timeReference high and low
198
       * @export @type {!Array<number>}
199
       */
200
      'timeReference': [0, 0],
201
      /** @export @type {number} */
202
      'version': 0, //WORD
203
      /** @export @type {string} */
204
      'UMID': '', // 64 chars
205
      /** @export @type {number} */
206
      'loudnessValue': 0, //WORD
207
      /** @export @type {number} */
208
      'loudnessRange': 0, //WORD
209
      /** @export @type {number} */
210
      'maxTruePeakLevel': 0, //WORD
211
      /** @export @type {number} */
212
      'maxMomentaryLoudness': 0, //WORD
213
      /** @export @type {number} */
214
      'maxShortTermLoudness': 0, //WORD
215
      /** @export @type {string} */
216
      'reserved': '', //180
217
      /** @export @type {string} */
218
      'codingHistory': '' // string, unlimited
219
    };
220
    /**
221
     * The data of the 'ds64' chunk.
222
     * Used only with RF64 files.
223
     * @type {!Object<string, *>}
224
     * @export
225
     */
226
    this.ds64 = {
227
      /** @type {string} */
228
      'chunkId': '',
229
      /** @export @type {number} */
230
      'chunkSize': 0,
231
      /** @export @type {number} */
232
      'riffSizeHigh': 0, // DWORD
233
      /** @export @type {number} */
234
      'riffSizeLow': 0, // DWORD
235
      /** @export @type {number} */
236
      'dataSizeHigh': 0, // DWORD
237
      /** @export @type {number} */
238
      'dataSizeLow': 0, // DWORD
239
      /** @export @type {number} */
240
      'originationTime': 0, // DWORD
241
      /** @export @type {number} */
242
      'sampleCountHigh': 0, // DWORD
243
      /** @export @type {number} */
244
      'sampleCountLow': 0, // DWORD
245
      /** @export @type {number} */
246
      //'tableLength': 0, // DWORD
247
      /** @export @type {!Array<number>} */
248
      //'table': []
249
    };
250
    /**
251
     * The data of the 'data' chunk.
252
     * @type {!Object<string, *>}
253
     * @export
254
     */
255
    this.data = {
256
      /** @export @type {string} */
257
      'chunkId': '',
258
      /** @export @type {number} */
259
      'chunkSize': 0,
260
      /** @export @type {!Array<number>} */
261
      'samples': []
262
    };
263
    /**
264
     * The data of the 'LIST' chunks.
265
     * Each item in this list must have this signature:
266
     *  {
267
     *    'chunkId': '',
268
     *    'chunkSize': 0,
269
     *    'format': '',
270
     *    'subChunks': []
271
     *   }
272
     * @type {!Array<!Object>}
273
     * @export
274
     */
275
    this.LIST = [];
276
    /**
277
     * The data of the 'junk' chunk.
278
     * @type {!Object<string, *>}
279
     * @export
280
     */
281
    this.junk = {
282
      /** @export @type {string} */
283
      'chunkId': '',
284
      /** @export @type {number} */
285
      'chunkSize': 0,
286
      /** @export @type {!Array<number>} */
287
      'chunkData': []
288
    };
289
    /**
290
     * If the data in data.samples is interleaved or not.
291
     * @type {boolean}
292
     * @export
293
     */
294
    this.isInterleaved = true;
295
    /**
296
     * The bit depth code according to the samples.
297
     * @type {string}
298
     * @export
299
     */
300
    this.bitDepth = '0';
301
    /**
302
     * Audio formats.
303
     * Formats not listed here will be set to 65534
304
     * and treated as WAVE_FORMAT_EXTENSIBLE
305
     * @enum {number}
306
     * @private
307
     */
308
    this.audioFormats_ = {
309
      '4': 17,
310
      '8': 1,
311
      '8a': 6,
312
      '8m': 7,
313
      '16': 1,
314
      '24': 1,
315
      '32': 1,
316
      '32f': 3,
317
      '64': 3
318
    };
319
    /**
320
     * @type {number}
321
     * @private
322
     */
323
    this.head_ = 0;
324
    // Load a file from the buffer if one was passed
325
    // when creating the object
326
    if(bytes) {
327
      this.fromBuffer(bytes);
328
    }
329
  }
330
331
  /**
332
   * Set up the WaveFile object based on the arguments passed.
333
   * @param {number} numChannels The number of channels
334
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
335
   * @param {number} sampleRate The sample rate.
336
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
337
   * @param {string} bitDepth The audio bit depth code.
338
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
339
   *    or any value between '8' and '32' (like '12').
340
   * @param {!Array<number>} samples Array of samples to be written.
341
   *    The samples must be in the correct range according to the
342
   *    bit depth.
343
   * @param {?Object} options Optional. Used to force the container
344
   *    as RIFX with {'container': 'RIFX'}
345
   * @throws {Error} If any argument does not meet the criteria.
346
   * @export
347
   */
348
  fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
349
    if (!options['container']) {
350
      options['container'] = 'RIFF';
351
    }
352
    this.bitDepth = bitDepth;
353
    // interleave the samples if they were passed de-interleaved
354
    this.data.samples = samples;
355
    if (samples.length > 0) {
356
      if (samples[0].constructor === Array) {
357
        this.isInterleaved = false;
358
        this.assureInterleaved_();
359
      }
360
    }
361
    /** @type {number} */
362
    let numBytes = (((parseInt(bitDepth, 10) - 1) | 7) + 1) / 8;
363
    this.createPCMHeader_(
364
      bitDepth, numChannels, sampleRate, numBytes, options);
365
    if (bitDepth == '4') {
366
      this.createADPCMHeader_(
367
        bitDepth, numChannels, sampleRate, numBytes, options);
368
    } else if (bitDepth == '8a' || bitDepth == '8m') {
369
      this.createALawMulawHeader_(
370
        bitDepth, numChannels, sampleRate, numBytes, options);
371
    } else if(Object.keys(this.audioFormats_).indexOf(bitDepth) == -1 ||
372
        this.fmt.numChannels > 2) {
373
      this.createExtensibleHeader_(
374
        bitDepth, numChannels, sampleRate, numBytes, options);
375
    }
376
    // the data chunk
377
    this.data.chunkId = 'data';
378
    this.data.chunkSize = this.data.samples.length * numBytes;
379
    this.validateHeader_();
380
    this.LEorBE_();
381
  }
382
383
  /**
384
   * Set up the WaveFile object from a byte buffer.
385
   * @param {!Uint8Array} bytes The buffer.
386
   * @throws {Error} If container is not RIFF, RIFX or RF64.
387
   * @throws {Error} If no 'fmt ' chunk is found.
388
   * @throws {Error} If no 'data' chunk is found.
389
   * @export
390
   */
391
  fromBuffer(bytes) {
392
    this.head_ = 0;
393
    this.clearHeader_();
394
    this.readRIFFChunk_(bytes);
395
    /** @type {!Object} */
396
    let chunk = riffIndex(bytes);
397
    this.readDs64Chunk_(bytes, chunk['subChunks']);
398
    this.readFmtChunk_(bytes, chunk['subChunks']);
399
    this.readFactChunk_(bytes, chunk['subChunks']);
400
    this.readBextChunk_(bytes, chunk['subChunks']);
401
    this.readCueChunk_(bytes, chunk['subChunks']);
402
    this.readSmplChunk_(bytes, chunk['subChunks']);
403
    this.readDataChunk_(bytes, chunk['subChunks']);
404
    this.readJunkChunk_(bytes, chunk['subChunks']);
405
    this.readLISTChunk_(bytes, chunk['subChunks']);
406
    this.bitDepthFromFmt_();
407
  }
408
409
  /**
410
   * Return a byte buffer representig the WaveFile object as a .wav file.
411
   * The return value of this method can be written straight to disk.
412
   * @return {!Uint8Array} A .wav file.
413
   * @throws {Error} If any property of the object appears invalid.
414
   * @export
415
   */
416
  toBuffer() {
417
    this.validateHeader_();
418
    this.assureInterleaved_();
419
    return this.createWaveFile_();
420
  }
421
422
  /**
423
   * Use a .wav file encoded as a base64 string to load the WaveFile object.
424
   * @param {string} base64String A .wav file as a base64 string.
425
   * @throws {Error} If any property of the object appears invalid.
426
   * @export
427
   */
428
  fromBase64(base64String) {
429
    this.fromBuffer(new Uint8Array(decode(base64String)));
430
  }
431
432
  /**
433
   * Return a base64 string representig the WaveFile object as a .wav file.
434
   * @return {string} A .wav file as a base64 string.
435
   * @throws {Error} If any property of the object appears invalid.
436
   * @export
437
   */
438
  toBase64() {
439
    return encode(this.toBuffer());
440
  }
441
442
  /**
443
   * Return a DataURI string representig the WaveFile object as a .wav file.
444
   * The return of this method can be used to load the audio in browsers.
445
   * @return {string} A .wav file as a DataURI.
446
   * @throws {Error} If any property of the object appears invalid.
447
   * @export
448
   */
449
  toDataURI() {
450
    return 'data:audio/wav;base64,' + this.toBase64();
451
  }
452
453
  /**
454
   * Use a .wav file encoded as a DataURI to load the WaveFile object.
455
   * @param {string} dataURI A .wav file as DataURI.
456
   * @throws {Error} If any property of the object appears invalid.
457
   * @export
458
   */
459
  fromDataURI(dataURI) {
460
    this.fromBase64(dataURI.replace('data:audio/wav;base64,', ''));
461
  }
462
463
  /**
464
   * Force a file as RIFF.
465
   * @export
466
   */
467
  toRIFF() {
468
    if (this.container == 'RF64') {
469
      this.fromScratch(
470
        this.fmt.numChannels,
471
        this.fmt.sampleRate,
472
        this.bitDepth,
473
        this.data.samples);
474
    } else {
475
      this.container = 'RIFF';
476
      this.LEorBE_();
477
    }
478
  }
479
480
  /**
481
   * Force a file as RIFX.
482
   * @export
483
   */
484
  toRIFX() {
485
    if (this.container == 'RF64') {
486
      this.fromScratch(
487
        this.fmt.numChannels,
488
        this.fmt.sampleRate,
489
        this.bitDepth,
490
        this.data.samples,
491
        {'container': 'RIFX'});
492
    } else {
493
      this.container = 'RIFX';
494
      this.LEorBE_();
495
    }
496
  }
497
498
  /**
499
   * Change the bit depth of the samples.
500
   * @param {string} bitDepth The new bit depth of the samples.
501
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
502
   * @param {boolean} changeResolution A boolean indicating if the
503
   *    resolution of samples should be actually changed or not.
504
   * @throws {Error} If the bit depth is not valid.
505
   * @export
506
   */
507
  toBitDepth(bitDepth, changeResolution=true) {
508
    let toBitDepth = bitDepth;
509
    let thisBitDepth = this.bitDepth;
510
    if (!changeResolution) {
511
      toBitDepth = this.realBitDepth_(bitDepth);
512
      thisBitDepth = this.realBitDepth_(this.bitDepth);
513
    }
514
    this.assureInterleaved_();
515
    this.assureUncompressed_();
516
    this.truncateSamples();
517
    bitdepth(this.data.samples, thisBitDepth, toBitDepth);
518
    this.fromScratch(
519
      this.fmt.numChannels,
520
      this.fmt.sampleRate,
521
      bitDepth,
522
      this.data.samples,
523
      {'container': this.correctContainer_()});
524
  }
525
526
  /**
527
   * Interleave multi-channel samples.
528
   * @export
529
   */
530
  interleave() {
531
    if (!this.isInterleaved) {
532
      /** @type {!Array<number>} */
533
      let finalSamples = [];
534
      for (let i=0; i < this.data.samples[0].length; i++) {
535
        for (let j=0; j < this.data.samples.length; j++) {
536
          finalSamples.push(this.data.samples[j][i]);
537
        }
538
      }
539
      this.data.samples = finalSamples;
540
      this.isInterleaved = true;
541
    }
542
  }
543
544
  /**
545
   * De-interleave samples into multiple channels.
546
   * @export
547
   */
548
  deInterleave() {
549
    if (this.isInterleaved) {
550
      /** @type {!Array<!Array<number>>} */
551
      let finalSamples = [];
552
      for (let i=0; i < this.fmt.numChannels; i++) {
553
        finalSamples[i] = [];
554
      }
555
      /** @type {number} */
556
      let len = this.data.samples.length;
557
      for (let i=0; i < len; i+=this.fmt.numChannels) {
558
        for (let j=0; j < this.fmt.numChannels; j++) {
559
          finalSamples[j].push(this.data.samples[i+j]);
560
        }
561
      }
562
      this.data.samples = finalSamples;
563
      this.isInterleaved = false;
564
    }
565
  }
566
567
  /**
568
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
569
   * @throws {Error} If sample rate is not 8000.
570
   * @throws {Error} If number of channels is not 1.
571
   * @export
572
   */
573
  toIMAADPCM() {
574
    if (this.fmt.sampleRate != 8000) {
575
      throw new Error(
576
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
577
    } else if(this.fmt.numChannels != 1) {
0 ignored issues
show
Best Practice introduced by
Comparing this.fmt.numChannels to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
578
      throw new Error(
579
        'Only mono files can be compressed as IMA-ADPCM.');
580
    } else {
581
      this.assure16Bit_();
582
      this.fromScratch(
583
        this.fmt.numChannels,
584
        this.fmt.sampleRate,
585
        '4',
586
        encodeADPCM(this.data.samples),
587
        {'container': this.correctContainer_()});
588
    }
589
  }
590
591
  /**
592
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
593
   * @param {string} bitDepth The new bit depth of the samples.
594
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
595
   *    Optional. Default is 16.
596
   * @export
597
   */
598
  fromIMAADPCM(bitDepth='16') {
599
    this.fromScratch(
600
      this.fmt.numChannels,
601
      this.fmt.sampleRate,
602
      '16',
603
      decodeADPCM(this.data.samples, this.fmt.blockAlign),
604
      {'container': this.correctContainer_()});
605
    if (bitDepth != '16') {
606
      this.toBitDepth(bitDepth);
607
    }
608
  }
609
610
  /**
611
   * Encode 16-bit wave file as 8-bit A-Law.
612
   * @export
613
   */
614
  toALaw() {
615
    this.assure16Bit_();
616
    this.assureInterleaved_();
617
    this.fromScratch(
618
      this.fmt.numChannels,
619
      this.fmt.sampleRate,
620
      '8a',
621
      alawmulaw.alaw.encode(this.data.samples),
622
      {'container': this.correctContainer_()});
623
  }
624
625
  /**
626
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
627
   * @param {string} bitDepth The new bit depth of the samples.
628
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
629
   *    Optional. Default is 16.
630
   * @export
631
   */
632
  fromALaw(bitDepth='16') {
633
    this.fromScratch(
634
      this.fmt.numChannels,
635
      this.fmt.sampleRate,
636
      '16',
637
      alawmulaw.alaw.decode(this.data.samples),
638
      {'container': this.correctContainer_()});
639
    if (bitDepth != '16') {
640
      this.toBitDepth(bitDepth);
641
    }
642
  }
643
644
  /**
645
   * Encode 16-bit wave file as 8-bit mu-Law.
646
   * @export
647
   */
648
  toMuLaw() {
649
    this.assure16Bit_();
650
    this.assureInterleaved_();
651
    this.fromScratch(
652
      this.fmt.numChannels,
653
      this.fmt.sampleRate,
654
      '8m',
655
      alawmulaw.mulaw.encode(this.data.samples),
656
      {'container': this.correctContainer_()});
657
  }
658
659
  /**
660
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
661
   * @param {string} bitDepth The new bit depth of the samples.
662
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
663
   *    Optional. Default is 16.
664
   * @export
665
   */
666
  fromMuLaw(bitDepth='16') {
667
    this.fromScratch(
668
      this.fmt.numChannels,
669
      this.fmt.sampleRate,
670
      '16',
671
      alawmulaw.mulaw.decode(this.data.samples),
672
      {'container': this.correctContainer_()});
673
    if (bitDepth != '16') {
674
      this.toBitDepth(bitDepth);
675
    }
676
  }
677
678
  /**
679
   * Write a RIFF tag in the INFO chunk. If the tag do not exist,
680
   * then it is created. It if exists, it is overwritten.
681
   * @param {string} tag The tag name.
682
   * @param {string} value The tag value.
683
   * @throws {Error} If the tag name is not valid.
684
   * @export
685
   */
686
  setTag(tag, value) {
687
    tag = this.fixTagName_(tag);
688
    /** @type {!Object} */
689
    let index = this.getTagIndex_(tag);
690
    if (index.TAG !== null) {
691
      this.LIST[index.LIST]['subChunks'][index.TAG]['chunkSize'] =
692
        value.length + 1;
693
      this.LIST[index.LIST]['subChunks'][index.TAG]['value'] = value;
694
    } else if (index.LIST !== null) {
695
      this.LIST[index.LIST]['subChunks'].push({
696
        'chunkId': tag,
697
        'chunkSize': value.length + 1,
698
        'value': value});
699
    } else {
700
      this.LIST.push({
701
        'chunkId': 'LIST',
702
        'chunkSize': 8 + value.length + 1,
703
        'format': 'INFO',
704
        'subChunks': []});
705
      this.LIST[this.LIST.length - 1]['subChunks'].push({
706
        'chunkId': tag,
707
        'chunkSize': value.length + 1,
708
        'value': value});
709
    }
710
  }
711
712
  /**
713
   * Return the value of a RIFF tag in the INFO chunk.
714
   * @param {string} tag The tag name.
715
   * @return {?string} The value if the tag is found, null otherwise.
716
   * @export
717
   */
718
  getTag(tag) {
719
    /** @type {!Object} */
720
    let index = this.getTagIndex_(tag);
721
    if (index.TAG !== null) {
722
      return this.LIST[index.LIST]['subChunks'][index.TAG]['value'];
723
    }
724
    return null;
725
  }
726
727
  /**
728
   * Remove a RIFF tag in the INFO chunk.
729
   * @param {string} tag The tag name.
730
   * @return {boolean} True if a tag was deleted.
731
   * @export
732
   */
733
  deleteTag(tag) {
734
    /** @type {!Object} */
735
    let index = this.getTagIndex_(tag);
736
    if (index.TAG !== null) {
737
      this.LIST[index.LIST]['subChunks'].splice(index.TAG, 1);
738
      return true;
739
    }
740
    return false;
741
  }
742
743
  /**
744
   * Create a cue point in the wave file.
745
   * @param {number} position The cue point position in milliseconds.
746
   * @param {string} labl The LIST adtl labl text of the marker. Optional.
747
   * @export
748
   */
749
  setCuePoint(position, labl='') {
750
    this.cue.chunkId = 'cue ';
751
    position = (position * this.fmt.sampleRate) / 1000;
752
    /** @type {!Array<!Object>} */
753
    let existingPoints = this.getCuePoints_();
754
    this.clearLISTadtl_();
755
    /** @type {number} */
756
    let len = this.cue.points.length;
757
    this.cue.points = [];
758
    /** @type {boolean} */
759
    let hasSet = false;
760
    if (len == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing len to 0 using the == operator is not safe. Consider using === instead.
Loading history...
761
      this.setCuePoint_(position, 1, labl);
762
    } else {
763
      for (let i=0; i<len; i++) {
764
        if (existingPoints[i]['dwPosition'] > position && !hasSet) {
765
          this.setCuePoint_(position, i + 1, labl);
766
          this.setCuePoint_(
767
            existingPoints[i]['dwPosition'],
768
            i + 2,
769
            existingPoints[i]['label']);
770
          hasSet = true;
771
        } else {
772
          this.setCuePoint_(
773
            existingPoints[i]['dwPosition'],
774
            i + 1,
775
            existingPoints[i]['label']);
776
        }
777
      }
778
      if (!hasSet) {
779
        this.setCuePoint_(position, this.cue.points.length + 1, labl);
780
      }
781
    }
782
    this.cue.dwCuePoints = this.cue.points.length;
783
  }
784
785
  /**
786
   * Remove a cue point from a wave file.
787
   * @param {number} index the index of the point. First is 1,
788
   *    second is 2, and so on.
789
   * @export
790
   */
791
  deleteCuePoint(index) {
792
    this.cue.chunkId = 'cue ';
793
    /** @type {!Array<!Object>} */
794
    let existingPoints = this.getCuePoints_();
795
    this.clearLISTadtl_();
796
    /** @type {number} */
797
    let len = this.cue.points.length;
798
    this.cue.points = [];
799
    for (let i=0; i<len; i++) {
800
      if (i + 1 != index) {
801
        this.setCuePoint_(
802
          existingPoints[i]['dwPosition'],
803
          i + 1,
804
          existingPoints[i]['label']);
805
      }
806
    }
807
    this.cue.dwCuePoints = this.cue.points.length;
808
    if (this.cue.dwCuePoints) {
809
      this.cue.chunkId = 'cue ';
810
    } else {
811
      this.cue.chunkId = '';
812
      this.clearLISTadtl_();
813
    }
814
  }
815
816
  /**
817
   * Update the label of a cue point.
818
   * @param {number} pointIndex The ID of the cue point.
819
   * @param {string} label The new text for the label.
820
   * @export
821
   */
822
  updateLabel(pointIndex, label) {
823
    /** @type {?number} */
824
    let adtlIndex = this.getAdtlChunk_();
825
    if (adtlIndex !== null) {
826
      for (let i=0; i<this.LIST[adtlIndex]['subChunks'].length; i++) {
827
        if (this.LIST[adtlIndex]['subChunks'][i]['dwName'] ==
828
            pointIndex) {
829
          this.LIST[adtlIndex]['subChunks'][i]['value'] = label;
830
        }
831
      }
832
    }
833
  }
834
835
  /**
836
   * Push a new cue point in this.cue.points.
837
   * @param {number} position The position in milliseconds.
838
   * @param {number} dwName the dwName of the cue point
839
   * @private
840
   */
841
  setCuePoint_(position, dwName, label) {
842
    this.cue.points.push({
843
      'dwName': dwName,
844
      'dwPosition': position,
845
      'fccChunk': 'data',
846
      'dwChunkStart': 0,
847
      'dwBlockStart': 0,
848
      'dwSampleOffset': position,
849
    });
850
    this.setLabl_(dwName, label);
851
  }
852
853
  /**
854
   * Return an array with the position of all cue points in the file.
855
   * @return {!Array<!Object>}
856
   * @private
857
   */
858
  getCuePoints_() {
859
    /** @type {!Array<!Object>} */
860
    let points = [];
861
    for (let i=0; i<this.cue.points.length; i++) {
862
      points.push({
863
        'dwPosition': this.cue.points[i]['dwPosition'],
864
        'label': this.getLabelForCuePoint_(
865
          this.cue.points[i]['dwName'])});
866
    }
867
    return points;
868
  }
869
870
  /**
871
   * Return the label of a cue point.
872
   * @param {number} pointDwName The ID of the cue point.
873
   * @return {string}
874
   * @private
875
   */
876
  getLabelForCuePoint_(pointDwName) {
877
    /** @type {?number} */
878
    let adtlIndex = this.getAdtlChunk_();
879
    if (adtlIndex !== null) {
880
      for (let i=0; i<this.LIST[adtlIndex]['subChunks'].length; i++) {
881
        if (this.LIST[adtlIndex]['subChunks'][i]['dwName'] ==
882
            pointDwName) {
883
          return this.LIST[adtlIndex]['subChunks'][i]['value'];
884
        }
885
      }
886
    }
887
    return '';
888
  }
889
890
  /**
891
   * Clear any LIST chunk labeled as 'adtl'.
892
   * @private
893
   */
894
  clearLISTadtl_() {
895
    for (let i=0; i<this.LIST.length; i++) {
896
      if (this.LIST[i]['format'] == 'adtl') {
897
        this.LIST.splice(i);
898
      }
899
    }
900
  }
901
902
  /**
903
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
904
   * @param {number} dwName The ID of the cue point.
905
   * @param {string} label The label for the cue point.
906
   * @private
907
   */
908
  setLabl_(dwName, label) {
909
    /** @type {?number} */
910
    let adtlIndex = this.getAdtlChunk_();
911
    if (adtlIndex === null) {
912
      this.LIST.push({
913
        'chunkId': 'LIST',
914
        'chunkSize': 4,
915
        'format': 'adtl',
916
        'subChunks': []});
917
      adtlIndex = this.LIST.length - 1;
918
    }
919
    this.setLabelText_(adtlIndex === null ? 0 : adtlIndex, dwName, label);
920
  }
921
922
  /**
923
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
924
   * @param {number} adtlIndex The index of the 'adtl' LIST in this.LIST.
925
   * @param {number} dwName The ID of the cue point.
926
   * @param {string} label The label for the cue point.
927
   * @private
928
   */
929
  setLabelText_(adtlIndex, dwName, label) {
930
    this.LIST[adtlIndex]['subChunks'].push({
931
      'chunkId': 'labl',
932
      'chunkSize': label.length,
933
      'dwName': dwName,
934
      'value': label
935
    });
936
    this.LIST[adtlIndex]['chunkSize'] += label.length + 4 + 4 + 4 + 1;
937
  }
938
939
  /**
940
   * Return the index of the 'adtl' LIST in this.LIST.
941
   * @return {?number}
942
   * @private
943
   */
944
  getAdtlChunk_() {
945
    for (let i=0; i<this.LIST.length; i++) {
946
      if(this.LIST[i]['format'] == 'adtl') {
947
        return i;
948
      }
949
    }
950
    return null;
951
  }
952
953
  /**
954
   * Return the index of a tag in a FILE chunk.
955
   * @param {string} tag The tag name.
956
   * @return {!Object<string, ?number>}
957
   *    Object.LIST is the INFO index in LIST
958
   *    Object.TAG is the tag index in the INFO
959
   * @private
960
   */
961
  getTagIndex_(tag) {
962
    /** @type {!Object<string, ?number>} */
963
    let index = {LIST: null, TAG: null};
964
    for (let i=0; i<this.LIST.length; i++) {
965
      if (this.LIST[i]['format'] == 'INFO') {
966
        index.LIST = i;
967
        for (let j=0; j<this.LIST[i]['subChunks'].length; j++) {
968
          if (this.LIST[i]['subChunks'][j]['chunkId'] == tag) {
969
            index.TAG = j;
970
            break;
971
          }
972
        }
973
        break;
974
      }
975
    }
976
    return index;
977
  }
978
979
  /**
980
   * Fix a RIFF tag format if possible, throw an error otherwise.
981
   * @param {string} tag The tag name.
982
   * @return {string} The tag name in proper fourCC format.
983
   * @private
984
   */
985
  fixTagName_(tag) {
986
    if (tag.constructor !== String) {
987
      throw new Error('Invalid tag name.');
988
    } else if(tag.length < 4) {
989
      for (let i=0; i<4-tag.length; i++) {
990
        tag += ' ';
991
      }
992
    }
993
    return tag;
994
  }
995
996
  /**
997
   * Create the header of a ADPCM wave file.
998
   * @param {string} bitDepth The audio bit depth
999
   * @param {number} numChannels The number of channels
1000
   * @param {number} sampleRate The sample rate.
1001
   * @param {number} numBytes The number of bytes each sample use.
1002
   * @param {!Object} options The extra options, like container defintion.
1003
   * @private
1004
   */
1005
  createADPCMHeader_(bitDepth, numChannels, sampleRate, numBytes, options) {
1006
    this.createPCMHeader_(
1007
      bitDepth, numChannels, sampleRate, numBytes, options);
1008
    this.chunkSize = 40 + this.data.samples.length;
1009
    this.fmt.chunkSize = 20;
1010
    this.fmt.byteRate = 4055;
1011
    this.fmt.blockAlign = 256;
1012
    this.fmt.bitsPerSample = 4;
1013
    this.fmt.cbSize = 2;
1014
    this.fmt.validBitsPerSample = 505;
1015
    this.fact.chunkId = 'fact';
1016
    this.fact.chunkSize = 4;
1017
    this.fact.dwSampleLength = this.data.samples.length * 2;
1018
    this.data.chunkSize = this.data.samples.length;
1019
  }
1020
1021
  /**
1022
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
1023
   * @param {string} bitDepth The audio bit depth
1024
   * @param {number} numChannels The number of channels
1025
   * @param {number} sampleRate The sample rate.
1026
   * @param {number} numBytes The number of bytes each sample use.
1027
   * @param {!Object} options The extra options, like container defintion.
1028
   * @private
1029
   */
1030
  createExtensibleHeader_(
1031
      bitDepth, numChannels, sampleRate, numBytes, options) {
1032
    this.createPCMHeader_(
1033
      bitDepth, numChannels, sampleRate, numBytes, options);
1034
    this.chunkSize = 36 + 24 + this.data.samples.length * numBytes;
1035
    this.fmt.chunkSize = 40;
1036
    this.fmt.bitsPerSample = ((parseInt(bitDepth, 10) - 1) | 7) + 1;
1037
    this.fmt.cbSize = 22;
1038
    this.fmt.validBitsPerSample = parseInt(bitDepth, 10);
1039
    this.fmt.dwChannelMask = this.getDwChannelMask_();
1040
    // subformat 128-bit GUID as 4 32-bit values
1041
    // only supports uncompressed integer PCM samples
1042
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
1043
  }
1044
1045
  /**
1046
   * Get the value for dwChannelMask according to the number of channels.
1047
   * @return {number} the dwChannelMask value.
1048
   * @private
1049
   */
1050
  getDwChannelMask_() {
1051
    /** @type {number} */
1052
    let dwChannelMask = 0;
1053
    // mono = FC
1054
    if (this.fmt.numChannels == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing this.fmt.numChannels to 1 using the == operator is not safe. Consider using === instead.
Loading history...
1055
      dwChannelMask = 0x4;
1056
    // stereo = FL, FR
1057
    } else if (this.fmt.numChannels == 2) {
1058
      dwChannelMask = 0x3;
1059
    // quad = FL, FR, BL, BR
1060
    } else if (this.fmt.numChannels == 4) {
1061
      dwChannelMask = 0x33;
1062
    // 5.1 = FL, FR, FC, LF, BL, BR
1063
    } else if (this.fmt.numChannels == 6) {
1064
      dwChannelMask = 0x3F;
1065
    // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
1066
    } else if (this.fmt.numChannels == 8) {
1067
      dwChannelMask = 0x63F;
1068
    }
1069
    return dwChannelMask;
1070
  }
1071
1072
  /**
1073
   * Create the header of mu-Law and A-Law wave files.
1074
   * @param {string} bitDepth The audio bit depth
1075
   * @param {number} numChannels The number of channels
1076
   * @param {number} sampleRate The sample rate.
1077
   * @param {number} numBytes The number of bytes each sample use.
1078
   * @param {!Object} options The extra options, like container defintion.
1079
   * @private
1080
   */
1081
  createALawMulawHeader_(
1082
      bitDepth, numChannels, sampleRate, numBytes, options) {
1083
    this.createPCMHeader_(
1084
      bitDepth, numChannels, sampleRate, numBytes, options);
1085
    this.chunkSize = 40 + this.data.samples.length;
1086
    this.fmt.chunkSize = 20;
1087
    this.fmt.cbSize = 2;
1088
    this.fmt.validBitsPerSample = 8;
1089
    this.fact.chunkId = 'fact';
1090
    this.fact.chunkSize = 4;
1091
    this.fact.dwSampleLength = this.data.samples.length;
1092
  }
1093
1094
  /**
1095
   * Create the header of a linear PCM wave file.
1096
   * @param {string} bitDepth The audio bit depth
1097
   * @param {number} numChannels The number of channels
1098
   * @param {number} sampleRate The sample rate.
1099
   * @param {number} numBytes The number of bytes each sample use.
1100
   * @param {!Object} options The extra options, like container defintion.
1101
   * @private
1102
   */
1103
  createPCMHeader_(bitDepth, numChannels, sampleRate, numBytes, options) {
1104
    this.clearHeader_();
1105
    this.container = options['container'];
1106
    this.chunkSize = 36 + this.data.samples.length * numBytes;
1107
    this.format = 'WAVE';
1108
    this.fmt.chunkId = 'fmt ';
1109
    this.fmt.chunkSize = 16;
1110
    this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
1111
    this.fmt.blockAlign = numChannels * numBytes;
1112
    this.fmt.audioFormat = this.audioFormats_[bitDepth] ?
1113
      this.audioFormats_[bitDepth] : 65534;
1114
    this.fmt.numChannels = numChannels;
1115
    this.fmt.sampleRate = sampleRate;
1116
    this.fmt.bitsPerSample = parseInt(bitDepth, 10);
1117
    this.fmt.cbSize = 0;
1118
    this.fmt.validBitsPerSample = 0;
1119
  }
1120
1121
  /**
1122
   * Return the closest greater number of bits for a number of bits that
1123
   * do not fill a full sequence of bytes.
1124
   * @param {string} bitDepth The bit depth.
1125
   * @return {string}
1126
   * @private
1127
   */
1128
  realBitDepth_(bitDepth) {
1129
    if (bitDepth != '32f') {
1130
      bitDepth = (((parseInt(bitDepth, 10) - 1) | 7) + 1).toString();
1131
    }
1132
    return bitDepth;
1133
  }
1134
1135
  /**
1136
   * Validate the header of the file.
1137
   * @throws {Error} If any property of the object appears invalid.
1138
   * @private
1139
   */
1140
  validateHeader_() {
1141
    this.validateBitDepth_();
1142
    this.validateNumChannels_();
1143
    this.validateSampleRate_();
1144
  }
1145
1146
  /**
1147
   * Validate the bit depth.
1148
   * @return {boolean} True is the bit depth is valid.
1149
   * @throws {Error} If bit depth is invalid.
1150
   * @private
1151
   */
1152
  validateBitDepth_() {
1153
    if (!this.audioFormats_[this.bitDepth]) {
1154
      if (parseInt(this.bitDepth, 10) > 8 &&
1155
          parseInt(this.bitDepth, 10) < 54) {
1156
        return true;
1157
      }
1158
      throw new Error('Invalid bit depth.');
1159
    }
1160
    return true;
1161
  }
1162
1163
  /**
1164
   * Validate the number of channels.
1165
   * @return {boolean} True is the number of channels is valid.
1166
   * @throws {Error} If the number of channels is invalid.
1167
   * @private
1168
   */
1169
  validateNumChannels_() {
1170
    /** @type {number} */
1171
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
1172
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
1173
      throw new Error('Invalid number of channels.');
1174
    }
1175
    return true;
1176
  }
1177
1178
  /**
1179
   * Validate the sample rate value.
1180
   * @return {boolean} True is the sample rate is valid.
1181
   * @throws {Error} If the sample rate is invalid.
1182
   * @private
1183
   */
1184
  validateSampleRate_() {
1185
    /** @type {number} */
1186
    let byteRate = this.fmt.numChannels *
1187
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
1188
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
1189
      throw new Error('Invalid sample rate.');
1190
    }
1191
    return true;
1192
  }
1193
1194
  /**
1195
   * Reset attributes that should emptied when a file is
1196
   * created with the fromScratch() or fromBuffer() methods.
1197
   * @private
1198
   */
1199
  clearHeader_() {
1200
    this.fmt.cbSize = 0;
1201
    this.fmt.validBitsPerSample = 0;
1202
    this.fact.chunkId = '';
1203
    this.ds64.chunkId = '';
1204
  }
1205
1206
  /**
1207
   * Make the file 16-bit if it is not.
1208
   * @private
1209
   */
1210
  assure16Bit_() {
1211
    this.assureUncompressed_();
1212
    if (this.bitDepth != '16') {
1213
      this.toBitDepth('16');
1214
    }
1215
  }
1216
1217
  /**
1218
   * Uncompress the samples in case of a compressed file.
1219
   * @private
1220
   */
1221
  assureUncompressed_() {
1222
    if (this.bitDepth == '8a') {
1223
      this.fromALaw();
1224
    } else if(this.bitDepth == '8m') {
1225
      this.fromMuLaw();
1226
    } else if (this.bitDepth == '4') {
1227
      this.fromIMAADPCM();
1228
    }
1229
  }
1230
1231
  /**
1232
   * Interleave the samples in case they are de-Interleaved.
1233
   * @private
1234
   */
1235
  assureInterleaved_() {
1236
    if (!this.isInterleaved) {
1237
      this.interleave();
1238
    }
1239
  }
1240
1241
  /**
1242
   * Set up to work wih big-endian or little-endian files.
1243
   * The types used are changed to LE or BE. If the
1244
   * the file is big-endian (RIFX), true is returned.
1245
   * @return {boolean} True if the file is RIFX.
1246
   * @private
1247
   */
1248
  LEorBE_() {
1249
    /** @type {boolean} */
1250
    let bigEndian = this.container === 'RIFX';
1251
    uInt16_['be'] = bigEndian;
1252
    uInt32_['be'] = bigEndian;
1253
    return bigEndian;
1254
  }
1255
1256
  /**
1257
   * Find a chunk by its fourCC_ in a array of RIFF chunks.
1258
   * @param {!Object} chunks The wav file chunks.
1259
   * @param {string} chunkId The chunk fourCC_.
1260
   * @param {boolean} multiple True if there may be multiple chunks
1261
   *    with the same chunkId.
1262
   * @return {Object|Array<!Object>|null}
1263
   * @private
1264
   */
1265
  findChunk_(chunks, chunkId, multiple=false) {
1266
    /** @type {!Array<!Object>} */
1267
    let chunk = [];
1268
    for (let i=0; i<chunks.length; i++) {
1269
      if (chunks[i]['chunkId'] == chunkId) {
1270
        if (multiple) {
1271
          chunk.push(chunks[i]);
1272
        } else {
1273
          return chunks[i];
1274
        }
1275
      }
1276
    }
1277
    if (chunkId == 'LIST') {
1278
      return chunk.length ? chunk : null;
1279
    }
1280
    return null;
1281
  }
1282
1283
  /**
1284
   * Read the RIFF chunk a wave file.
1285
   * @param {!Uint8Array} bytes A wav buffer.
1286
   * @throws {Error} If no 'RIFF' chunk is found.
1287
   * @private
1288
   */
1289
  readRIFFChunk_(bytes) {
1290
    this.head_ = 0;
1291
    this.container = this.readString_(bytes, 4);
1292
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
1293
      throw Error('Not a supported format.');
1294
    }
1295
    this.LEorBE_();
1296
    this.chunkSize = this.read_(bytes, uInt32_);
1297
    this.format = this.readString_(bytes, 4);
1298
    if (this.format != 'WAVE') {
1299
      throw Error('Could not find the "WAVE" format identifier');
1300
    }
1301
  }
1302
1303
  /**
1304
   * Read the 'fmt ' chunk of a wave file.
1305
   * @param {!Uint8Array} buffer The wav file buffer.
1306
   * @param {!Object} signature The file signature.
1307
   * @throws {Error} If no 'fmt ' chunk is found.
1308
   * @private
1309
   */
1310
  readFmtChunk_(buffer, signature) {
1311
    /** @type {?Object} */
1312
    let chunk = this.findChunk_(signature, 'fmt ');
1313
    if (chunk) {
1314
      this.head_ = chunk['chunkData']['start'];
1315
      this.fmt.chunkId = chunk['chunkId'];
1316
      this.fmt.chunkSize = chunk['chunkSize'];
1317
      this.fmt.audioFormat = this.read_(buffer, uInt16_);
1318
      this.fmt.numChannels = this.read_(buffer, uInt16_);
1319
      this.fmt.sampleRate = this.read_(buffer, uInt32_);
1320
      this.fmt.byteRate = this.read_(buffer, uInt32_);
1321
      this.fmt.blockAlign = this.read_(buffer, uInt16_);
1322
      this.fmt.bitsPerSample = this.read_(buffer, uInt16_);
1323
      this.readFmtExtension_(buffer);
1324
    } else {
1325
      throw Error('Could not find the "fmt " chunk');
1326
    }
1327
  }
1328
1329
  /**
1330
   * Read the 'fmt ' chunk extension.
1331
   * @param {!Uint8Array} buffer The wav file buffer.
1332
   * @private
1333
   */
1334
  readFmtExtension_(buffer) {
1335
    if (this.fmt.chunkSize > 16) {
1336
      this.fmt.cbSize = this.read_(buffer, uInt16_);
1337
      if (this.fmt.chunkSize > 18) {
1338
        this.fmt.validBitsPerSample = this.read_(buffer, uInt16_);
1339
        if (this.fmt.chunkSize > 20) {
1340
          this.fmt.dwChannelMask = this.read_(buffer, uInt32_);
1341
          this.fmt.subformat = [
1342
            this.read_(buffer, uInt32_),
1343
            this.read_(buffer, uInt32_),
1344
            this.read_(buffer, uInt32_),
1345
            this.read_(buffer, uInt32_)];
1346
        }
1347
      }
1348
    }
1349
  }
1350
1351
  /**
1352
   * Read the 'fact' chunk of a wav file.
1353
   * @param {!Uint8Array} buffer The wav file buffer.
1354
   * @param {!Object} signature The file signature.
1355
   * @private
1356
   */
1357
  readFactChunk_(buffer, signature) {
1358
    /** @type {?Object} */
1359
    let chunk = this.findChunk_(signature, 'fact');
1360
    if (chunk) {
1361
      this.head_ = chunk['chunkData']['start'];
1362
      this.fact.chunkId = chunk['chunkId'];
1363
      this.fact.chunkSize = chunk['chunkSize'];
1364
      this.fact.dwSampleLength = this.read_(buffer, uInt32_);
1365
    }
1366
  }
1367
1368
  /**
1369
   * Read the 'cue ' chunk of a wave file.
1370
   * @param {!Uint8Array} buffer The wav file buffer.
1371
   * @param {!Object} signature The file signature.
1372
   * @private
1373
   */
1374
  readCueChunk_(buffer, signature) {
1375
    /** @type {?Object} */
1376
    let chunk = this.findChunk_(signature, 'cue ');
1377
    if (chunk) {
1378
      this.head_ = chunk['chunkData']['start'];
1379
      this.cue.chunkId = chunk['chunkId'];
1380
      this.cue.chunkSize = chunk['chunkSize'];
1381
      this.cue.dwCuePoints = this.read_(buffer, uInt32_);
1382
      for (let i=0; i<this.cue.dwCuePoints; i++) {
1383
        this.cue.points.push({
1384
          'dwName': this.read_(buffer, uInt32_),
1385
          'dwPosition': this.read_(buffer, uInt32_),
1386
          'fccChunk': this.readString_(buffer, 4),
1387
          'dwChunkStart': this.read_(buffer, uInt32_),
1388
          'dwBlockStart': this.read_(buffer, uInt32_),
1389
          'dwSampleOffset': this.read_(buffer, uInt32_),
1390
        });
1391
      }
1392
    }
1393
  }
1394
1395
  /**
1396
   * Read the 'smpl' chunk of a wave file.
1397
   * @param {!Uint8Array} buffer The wav file buffer.
1398
   * @param {!Object} signature The file signature.
1399
   * @private
1400
   */
1401
  readSmplChunk_(buffer, signature) {
1402
    /** @type {?Object} */
1403
    let chunk = this.findChunk_(signature, 'smpl');
1404
    if (chunk) {
1405
      this.head_ = chunk['chunkData']['start'];
1406
      this.smpl.chunkId = chunk['chunkId'];
1407
      this.smpl.chunkSize = chunk['chunkSize'];
1408
      this.smpl.dwManufacturer = this.read_(buffer, uInt32_);
1409
      this.smpl.dwProduct = this.read_(buffer, uInt32_);
1410
      this.smpl.dwSamplePeriod = this.read_(buffer, uInt32_);
1411
      this.smpl.dwMIDIUnityNote = this.read_(buffer, uInt32_);
1412
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, uInt32_);
1413
      this.smpl.dwSMPTEFormat = this.read_(buffer, uInt32_);
1414
      this.smpl.dwSMPTEOffset = this.read_(buffer, uInt32_);
1415
      this.smpl.dwNumSampleLoops = this.read_(buffer, uInt32_);
1416
      this.smpl.dwSamplerData = this.read_(buffer, uInt32_);
1417
      for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
1418
        this.smpl.loops.push({
1419
          'dwName': this.read_(buffer, uInt32_),
1420
          'dwType': this.read_(buffer, uInt32_),
1421
          'dwStart': this.read_(buffer, uInt32_),
1422
          'dwEnd': this.read_(buffer, uInt32_),
1423
          'dwFraction': this.read_(buffer, uInt32_),
1424
          'dwPlayCount': this.read_(buffer, uInt32_),
1425
        });
1426
      }
1427
    }
1428
  }
1429
1430
  /**
1431
   * Read the 'data' chunk of a wave file.
1432
   * @param {!Uint8Array} buffer The wav file buffer.
1433
   * @param {!Object} signature The file signature.
1434
   * @throws {Error} If no 'data' chunk is found.
1435
   * @private
1436
   */
1437
  readDataChunk_(buffer, signature) {
1438
    /** @type {?Object} */
1439
    let chunk = this.findChunk_(signature, 'data');
1440
    if (chunk) {
1441
      this.data.chunkId = 'data';
1442
      this.data.chunkSize = chunk['chunkSize'];
1443
      this.samplesFromBytes_(buffer, chunk);
1444
    } else {
1445
      throw Error('Could not find the "data" chunk');
1446
    }
1447
  }
1448
1449
  /**
1450
   * Read the 'bext' chunk of a wav file.
1451
   * @param {!Uint8Array} buffer The wav file buffer.
1452
   * @param {!Object} signature The file signature.
1453
   * @private
1454
   */
1455
  readBextChunk_(buffer, signature) {
1456
    /** @type {?Object} */
1457
    let chunk = this.findChunk_(signature, 'bext');
1458
    if (chunk) {
1459
      this.head_ = chunk['chunkData']['start'];
1460
      this.bext.chunkId = chunk['chunkId'];
1461
      this.bext.chunkSize = chunk['chunkSize'];
1462
      this.bext.description = this.readString_(buffer, 256);
1463
      this.bext.originator = this.readString_(buffer, 32);
1464
      this.bext.originatorReference = this.readString_(buffer, 32);
1465
      this.bext.originationDate = this.readString_(buffer, 10);
1466
      this.bext.originationTime = this.readString_(buffer, 8);
1467
      this.bext.timeReference = [
1468
        this.read_(buffer, uInt32_),
1469
        this.read_(buffer, uInt32_)];
1470
      this.bext.version = this.read_(buffer, uInt16_);
1471
      this.bext.UMID = this.readString_(buffer, 64);
1472
      this.bext.loudnessValue = this.read_(buffer, uInt16_);
1473
      this.bext.loudnessRange = this.read_(buffer, uInt16_);
1474
      this.bext.maxTruePeakLevel = this.read_(buffer, uInt16_);
1475
      this.bext.maxMomentaryLoudness = this.read_(buffer, uInt16_);
1476
      this.bext.maxShortTermLoudness = this.read_(buffer, uInt16_);
1477
      this.bext.reserved = this.readString_(buffer, 180);
1478
      this.bext.codingHistory = this.readString_(
1479
        buffer, this.bext.chunkSize - 602);
1480
    }
1481
  }
1482
1483
  /**
1484
   * Read the 'ds64' chunk of a wave file.
1485
   * @param {!Uint8Array} buffer The wav file buffer.
1486
   * @param {!Object} signature The file signature.
1487
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1488
   * @private
1489
   */
1490
  readDs64Chunk_(buffer, signature) {
1491
    /** @type {?Object} */
1492
    let chunk = this.findChunk_(signature, 'ds64');
1493
    if (chunk) {
1494
      this.head_ = chunk['chunkData']['start'];
1495
      this.ds64.chunkId = chunk['chunkId'];
1496
      this.ds64.chunkSize = chunk['chunkSize'];
1497
      this.ds64.riffSizeHigh = this.read_(buffer, uInt32_);
1498
      this.ds64.riffSizeLow = this.read_(buffer, uInt32_);
1499
      this.ds64.dataSizeHigh = this.read_(buffer, uInt32_);
1500
      this.ds64.dataSizeLow = this.read_(buffer, uInt32_);
1501
      this.ds64.originationTime = this.read_(buffer, uInt32_);
1502
      this.ds64.sampleCountHigh = this.read_(buffer, uInt32_);
1503
      this.ds64.sampleCountLow = this.read_(buffer, uInt32_);
1504
      //if (this.ds64.chunkSize > 28) {
1505
      //  this.ds64.tableLength = unpack(
1506
      //    chunkData.slice(28, 32), uInt32_);
1507
      //  this.ds64.table = chunkData.slice(
1508
      //     32, 32 + this.ds64.tableLength); 
1509
      //}
1510
    } else {
1511
      if (this.container == 'RF64') {
1512
        throw Error('Could not find the "ds64" chunk');  
1513
      }
1514
    }
1515
  }
1516
1517
  /**
1518
   * Read the 'LIST' chunks of a wave file.
1519
   * @param {!Uint8Array} buffer The wav file buffer.
1520
   * @param {!Object} signature The file signature.
1521
   * @private
1522
   */
1523
  readLISTChunk_(buffer, signature) {
1524
    /** @type {?Object} */
1525
    let listChunks = this.findChunk_(signature, 'LIST', true);
1526
    if (listChunks === null) {
1527
      return;
1528
    }
1529
    for (let j=0; j < listChunks.length; j++) {
1530
      /** @type {!Object} */
1531
      let subChunk = listChunks[j];
1532
      this.LIST.push({
1533
        'chunkId': subChunk['chunkId'],
1534
        'chunkSize': subChunk['chunkSize'],
1535
        'format': subChunk['format'],
1536
        'subChunks': []});
1537
      for (let x=0; x<subChunk['subChunks'].length; x++) {
1538
        this.readLISTSubChunks_(subChunk['subChunks'][x],
1539
          subChunk['format'], buffer);
1540
      }
1541
    }
1542
  }
1543
1544
  /**
1545
   * Read the sub chunks of a 'LIST' chunk.
1546
   * @param {!Object} subChunk The 'LIST' subchunks.
1547
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1548
   * @param {!Uint8Array} buffer The wav file buffer.
1549
   * @private
1550
   */
1551
  readLISTSubChunks_(subChunk, format, buffer) {
1552
    if (format == 'adtl') {
1553
      if (['labl', 'note','ltxt'].indexOf(subChunk['chunkId']) > -1) {
1554
        this.head_ = subChunk['chunkData']['start'];
1555
        /** @type {!Object<string, string|number>} */
1556
        let item = {
1557
          'chunkId': subChunk['chunkId'],
1558
          'chunkSize': subChunk['chunkSize'],
1559
          'dwName': this.read_(buffer, uInt32_)
1560
        };
1561
        if (subChunk['chunkId'] == 'ltxt') {
1562
          item['dwSampleLength'] = this.read_(buffer, uInt32_);
1563
          item['dwPurposeID'] = this.read_(buffer, uInt32_);
1564
          item['dwCountry'] = this.read_(buffer, uInt16_);
1565
          item['dwLanguage'] = this.read_(buffer, uInt16_);
1566
          item['dwDialect'] = this.read_(buffer, uInt16_);
1567
          item['dwCodePage'] = this.read_(buffer, uInt16_);
1568
        }
1569
        item['value'] = this.readZSTR_(buffer, this.head_);
1570
        this.LIST[this.LIST.length - 1]['subChunks'].push(item);
1571
      }
1572
    // RIFF INFO tags like ICRD, ISFT, ICMT
1573
    } else if(format == 'INFO') {
1574
      this.head_ = subChunk['chunkData']['start'];
1575
      this.LIST[this.LIST.length - 1]['subChunks'].push({
1576
        'chunkId': subChunk['chunkId'],
1577
        'chunkSize': subChunk['chunkSize'],
1578
        'value': this.readZSTR_(buffer,  this.head_)
1579
      });
1580
    }
1581
  }
1582
1583
  /**
1584
   * Read the 'junk' chunk of a wave file.
1585
   * @param {!Uint8Array} buffer The wav file buffer.
1586
   * @param {!Object} signature The file signature.
1587
   * @private
1588
   */
1589
  readJunkChunk_(buffer, signature) {
1590
    /** @type {?Object} */
1591
    let chunk = this.findChunk_(signature, 'junk');
1592
    if (chunk) {
1593
      this.junk = {
1594
        'chunkId': chunk['chunkId'],
1595
        'chunkSize': chunk['chunkSize'],
1596
        'chunkData': [].slice.call(buffer.slice(
1597
          chunk['chunkData']['start'],
1598
          chunk['chunkData']['end']))
1599
      };
1600
    }
1601
  }
1602
1603
  /**
1604
   * Read bytes as a ZSTR string.
1605
   * @param {!Array<number>|!Uint8Array} bytes The bytes.
1606
   * @return {string} The string.
1607
   * @private
1608
   */
1609
  readZSTR_(bytes, index=0) {
1610
    /** @type {string} */
1611
    let str = '';
1612
    for (let i=index; i<bytes.length; i++) {
1613
      this.head_++;
1614
      if (bytes[i] === 0) {
1615
        break;
1616
      }
1617
      str += unpackFrom(bytes, types.chr, i);
1618
    }
1619
    return str;
1620
  }
1621
1622
  /**
1623
   * Read bytes as a string from a RIFF chunk.
1624
   * @param {!Array<number>|!Uint8Array} bytes The bytes.
1625
   * @param {number} maxSize the max size of the string.
1626
   * @return {string} The string.
1627
   * @private
1628
   */
1629
  readString_(bytes, maxSize) {
1630
    /** @type {string} */
1631
    let str = '';
1632
    for (let i=0; i<maxSize; i++) {
1633
      str += unpackFrom(bytes, types.chr, this.head_);
1634
      this.head_++;
1635
    }
1636
    return str;
1637
  }
1638
1639
  /**
1640
   * Read a number from a chunk.
1641
   * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
1642
   * @param {!Object} bdType The type definition.
1643
   * @return {number} The number.
1644
   * @private
1645
   */
1646
  read_(bytes, bdType) {
1647
    /** @type {number} */
1648
    let size = bdType['bits'] / 8;
1649
    /** @type {number} */
1650
    let value = unpackFrom(bytes, bdType, this.head_);
1651
    this.head_ += size;
1652
    return value;
1653
  }
1654
1655
  /**
1656
   * Write a variable size string as bytes. If the string is smaller
1657
   * than the max size the output array is filled with 0s.
1658
   * @param {string} str The string to be written as bytes.
1659
   * @param {number} maxSize the max size of the string.
1660
   * @return {!Array<number>} The bytes.
1661
   * @private
1662
   */
1663
  writeString_(str, maxSize, push=true) {
1664
    /** @type {!Array<number>} */   
1665
    let bytes = packArray(str, types.chr);
1666
    if (push) {
1667
      for (let i=bytes.length; i<maxSize; i++) {
1668
        bytes.push(0);
1669
      }  
1670
    }
1671
    return bytes;
1672
  }
1673
1674
  /**
1675
   * Turn the samples to bytes.
1676
   * @return {!Array<number>} The bytes.
1677
   * @private
1678
   */
1679
  samplesToBytes_() {
1680
    return packArray(
1681
      this.data.samples, this.getSamplesType_());
1682
  }
1683
1684
  /**
1685
   * Truncate float samples on over and underflow.
1686
   * @private
1687
   */
1688
  truncateSamples() {
1689
    if (this.fmt.audioFormat == 3) {
1690
      /** @type {number} */   
1691
      let len = this.data.samples.length;
1692
      for (let i=0; i<len; i++) {
1693
        if (this.data.samples[i] > 1) {
1694
          this.data.samples[i] = 1;
1695
        } else if (this.data.samples[i] < -1) {
1696
          this.data.samples[i] = -1;
1697
        }
1698
      }
1699
    }
1700
  }
1701
1702
  /**
1703
   * Turn bytes to samples and load them in the data.samples property.
1704
   * @param {!Uint8Array} bytes The bytes.
1705
   * @private
1706
   */
1707
  samplesFromBytes_(bytes, chunkData) {
1708
    this.data.samples = unpackArrayFrom(
1709
      bytes,
1710
      this.getSamplesType_(),
1711
      chunkData['chunkData']['start'],
1712
      chunkData['chunkData']['end']);
1713
  }
1714
1715
  /**
1716
   * Get the data type definition for the samples.
1717
   * @return {!Object<string, number|boolean>} The type definition.
1718
   * @private
1719
   */
1720
  getSamplesType_() {
1721
    /** @type {!Object<string, number|boolean>} */
1722
    let bdType = {
1723
      'be': this.container === 'RIFX',
1724
      'bits': this.fmt.bitsPerSample == 4 ? 8 : this.fmt.bitsPerSample,
1725
      'float': this.fmt.audioFormat == 3 ? true : false
1726
    };
1727
    bdType['signed'] = bdType['bits'] == 8 ? false : true;
1728
    return bdType;
1729
  }
1730
1731
  /**
1732
   * Return the bytes of the 'bext' chunk.
1733
   * @return {!Array<number>} The 'bext' chunk bytes.
1734
   * @private
1735
   */
1736
  getBextBytes_() {
1737
    /** @type {!Array<number>} */
1738
    let bytes = [];
1739
    this.enforceBext_();
1740
    if (this.bext.chunkId) {
1741
      bytes = bytes.concat(
1742
        pack(this.bext.chunkId, types.fourCC),
1743
        pack(602 + this.bext.codingHistory.length, uInt32_),
1744
        this.writeString_(this.bext.description, 256),
1745
        this.writeString_(this.bext.originator, 32),
1746
        this.writeString_(this.bext.originatorReference, 32),
1747
        this.writeString_(this.bext.originationDate, 10),
1748
        this.writeString_(this.bext.originationTime, 8),
1749
        pack(this.bext.timeReference[0], uInt32_),
1750
        pack(this.bext.timeReference[1], uInt32_),
1751
        pack(this.bext.version, uInt16_),
1752
        this.writeString_(this.bext.UMID, 64),
1753
        pack(this.bext.loudnessValue, uInt16_),
1754
        pack(this.bext.loudnessRange, uInt16_),
1755
        pack(this.bext.maxTruePeakLevel, uInt16_),
1756
        pack(this.bext.maxMomentaryLoudness, uInt16_),
1757
        pack(this.bext.maxShortTermLoudness, uInt16_),
1758
        this.writeString_(this.bext.reserved, 180),
1759
        this.writeString_(
1760
          this.bext.codingHistory, this.bext.codingHistory.length));
1761
    }
1762
    return bytes;
1763
  }
1764
1765
  /**
1766
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
1767
   * @private
1768
   */
1769
  enforceBext_() {
1770
    for (var prop in this.bext) {
1771
      if (this.bext.hasOwnProperty(prop)) {
1772
        if (this.bext[prop] && prop != 'timeReference') {
1773
          this.bext.chunkId = 'bext';
1774
          break;
1775
        }
1776
      }
1777
    }
1778
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
1779
      this.bext.chunkId = 'bext';
1780
    }
1781
  }
1782
1783
  /**
1784
   * Return the bytes of the 'ds64' chunk.
1785
   * @return {!Array<number>} The 'ds64' chunk bytes.
1786
   * @private
1787
   */
1788
  getDs64Bytes_() {
1789
    /** @type {!Array<number>} */
1790
    let bytes = [];
1791
    if (this.ds64.chunkId) {
1792
      bytes = bytes.concat(
1793
        pack(this.ds64.chunkId, types.fourCC),
1794
        pack(this.ds64.chunkSize, uInt32_),
1795
        pack(this.ds64.riffSizeHigh, uInt32_),
1796
        pack(this.ds64.riffSizeLow, uInt32_),
1797
        pack(this.ds64.dataSizeHigh, uInt32_),
1798
        pack(this.ds64.dataSizeLow, uInt32_),
1799
        pack(this.ds64.originationTime, uInt32_),
1800
        pack(this.ds64.sampleCountHigh, uInt32_),
1801
        pack(this.ds64.sampleCountLow, uInt32_));
1802
    }
1803
    //if (this.ds64.tableLength) {
1804
    //  ds64Bytes = ds64Bytes.concat(
1805
    //    pack(this.ds64.tableLength, uInt32_),
1806
    //    this.ds64.table);
1807
    //}
1808
    return bytes;
1809
  }
1810
1811
  /**
1812
   * Return the bytes of the 'cue ' chunk.
1813
   * @return {!Array<number>} The 'cue ' chunk bytes.
1814
   * @private
1815
   */
1816
  getCueBytes_() {
1817
    /** @type {!Array<number>} */
1818
    let bytes = [];
1819
    if (this.cue.chunkId) {
1820
      /** @type {!Array<number>} */
1821
      let cuePointsBytes = this.getCuePointsBytes_();
1822
      bytes = bytes.concat(
1823
        pack(this.cue.chunkId, types.fourCC),
1824
        pack(cuePointsBytes.length + 4, uInt32_),
1825
        pack(this.cue.dwCuePoints, uInt32_),
1826
        cuePointsBytes);
1827
    }
1828
    return bytes;
1829
  }
1830
1831
  /**
1832
   * Return the bytes of the 'cue ' points.
1833
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
1834
   * @private
1835
   */
1836
  getCuePointsBytes_() {
1837
    /** @type {!Array<number>} */
1838
    let points = [];
1839
    for (let i=0; i<this.cue.dwCuePoints; i++) {
1840
      points = points.concat(
1841
        pack(this.cue.points[i]['dwName'], uInt32_),
1842
        pack(this.cue.points[i]['dwPosition'], uInt32_),
1843
        pack(this.cue.points[i]['fccChunk'], types.fourCC),
1844
        pack(this.cue.points[i]['dwChunkStart'], uInt32_),
1845
        pack(this.cue.points[i]['dwBlockStart'], uInt32_),
1846
        pack(this.cue.points[i]['dwSampleOffset'], uInt32_));
1847
    }
1848
    return points;
1849
  }
1850
1851
  /**
1852
   * Return the bytes of the 'smpl' chunk.
1853
   * @return {!Array<number>} The 'smpl' chunk bytes.
1854
   * @private
1855
   */
1856
  getSmplBytes_() {
1857
    /** @type {!Array<number>} */
1858
    let bytes = [];
1859
    if (this.smpl.chunkId) {
1860
      /** @type {!Array<number>} */
1861
      let smplLoopsBytes = this.getSmplLoopsBytes_();
1862
      bytes = bytes.concat(
1863
        pack(this.smpl.chunkId, types.fourCC),
1864
        pack(smplLoopsBytes.length + 36, uInt32_),
1865
        pack(this.smpl.dwManufacturer, uInt32_),
1866
        pack(this.smpl.dwProduct, uInt32_),
1867
        pack(this.smpl.dwSamplePeriod, uInt32_),
1868
        pack(this.smpl.dwMIDIUnityNote, uInt32_),
1869
        pack(this.smpl.dwMIDIPitchFraction, uInt32_),
1870
        pack(this.smpl.dwSMPTEFormat, uInt32_),
1871
        pack(this.smpl.dwSMPTEOffset, uInt32_),
1872
        pack(this.smpl.dwNumSampleLoops, uInt32_),
1873
        pack(this.smpl.dwSamplerData, uInt32_),
1874
        smplLoopsBytes);
1875
    }
1876
    return bytes;
1877
  }
1878
1879
  /**
1880
   * Return the bytes of the 'smpl' loops.
1881
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
1882
   * @private
1883
   */
1884
  getSmplLoopsBytes_() {
1885
    /** @type {!Array<number>} */
1886
    let loops = [];
1887
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
1888
      loops = loops.concat(
1889
        pack(this.smpl.loops[i]['dwName'], uInt32_),
1890
        pack(this.smpl.loops[i]['dwType'], uInt32_),
1891
        pack(this.smpl.loops[i]['dwStart'], uInt32_),
1892
        pack(this.smpl.loops[i]['dwEnd'], uInt32_),
1893
        pack(this.smpl.loops[i]['dwFraction'], uInt32_),
1894
        pack(this.smpl.loops[i]['dwPlayCount'], uInt32_));
1895
    }
1896
    return loops;
1897
  }
1898
1899
  /**
1900
   * Return the bytes of the 'fact' chunk.
1901
   * @return {!Array<number>} The 'fact' chunk bytes.
1902
   * @private
1903
   */
1904
  getFactBytes_() {
1905
    /** @type {!Array<number>} */
1906
    let bytes = [];
1907
    if (this.fact.chunkId) {
1908
      bytes = bytes.concat(
1909
        pack(this.fact.chunkId, types.fourCC),
1910
        pack(this.fact.chunkSize, uInt32_),
1911
        pack(this.fact.dwSampleLength, uInt32_));
1912
    }
1913
    return bytes;
1914
  }
1915
1916
  /**
1917
   * Return the bytes of the 'fmt ' chunk.
1918
   * @return {!Array<number>} The 'fmt' chunk bytes.
1919
   * @throws {Error} if no 'fmt ' chunk is present.
1920
   * @private
1921
   */
1922
  getFmtBytes_() {
1923
    if (this.fmt.chunkId) {
1924
      return [].concat(
1925
        pack(this.fmt.chunkId, types.fourCC),
1926
        pack(this.fmt.chunkSize, uInt32_),
1927
        pack(this.fmt.audioFormat, uInt16_),
1928
        pack(this.fmt.numChannels, uInt16_),
1929
        pack(this.fmt.sampleRate, uInt32_),
1930
        pack(this.fmt.byteRate, uInt32_),
1931
        pack(this.fmt.blockAlign, uInt16_),
1932
        pack(this.fmt.bitsPerSample, uInt16_),
1933
        this.getFmtExtensionBytes_());
1934
    }
1935
    throw Error('Could not find the "fmt " chunk');
1936
  }
1937
1938
  /**
1939
   * Return the bytes of the fmt extension fields.
1940
   * @return {!Array<number>} The fmt extension bytes.
1941
   * @private
1942
   */
1943
  getFmtExtensionBytes_() {
1944
    /** @type {!Array<number>} */
1945
    let extension = [];
1946
    if (this.fmt.chunkSize > 16) {
1947
      extension = extension.concat(
1948
        pack(this.fmt.cbSize, uInt16_));
1949
    }
1950
    if (this.fmt.chunkSize > 18) {
1951
      extension = extension.concat(
1952
        pack(this.fmt.validBitsPerSample, uInt16_));
1953
    }
1954
    if (this.fmt.chunkSize > 20) {
1955
      extension = extension.concat(
1956
        pack(this.fmt.dwChannelMask, uInt32_));
1957
    }
1958
    if (this.fmt.chunkSize > 24) {
1959
      extension = extension.concat(
1960
        pack(this.fmt.subformat[0], uInt32_),
1961
        pack(this.fmt.subformat[1], uInt32_),
1962
        pack(this.fmt.subformat[2], uInt32_),
1963
        pack(this.fmt.subformat[3], uInt32_));
1964
    }
1965
    return extension;
1966
  }
1967
1968
  /**
1969
   * Return the bytes of the 'LIST' chunk.
1970
   * @return {!Array<number>} The 'LIST' chunk bytes.
1971
   * @export for tests
1972
   */
1973
  getLISTBytes_() {
1974
    /** @type {!Array<number>} */
1975
    let bytes = [];
1976
    for (let i=0; i<this.LIST.length; i++) {
1977
      /** @type {!Array<number>} */
1978
      let subChunksBytes = this.getLISTSubChunksBytes_(
1979
          this.LIST[i]['subChunks'], this.LIST[i]['format']);
1980
      bytes = bytes.concat(
1981
        pack(this.LIST[i]['chunkId'], types.fourCC),
1982
        pack(subChunksBytes.length + 4, uInt32_),
1983
        pack(this.LIST[i]['format'], types.fourCC),
1984
        subChunksBytes);
1985
    }
1986
    return bytes;
1987
  }
1988
1989
  /**
1990
   * Return the bytes of the sub chunks of a 'LIST' chunk.
1991
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
1992
   * @param {string} format The format of the 'LIST' chunk.
1993
   *    Currently supported values are 'adtl' or 'INFO'.
1994
   * @return {!Array<number>} The sub chunk bytes.
1995
   * @private
1996
   */
1997
  getLISTSubChunksBytes_(subChunks, format) {
1998
    /** @type {!Array<number>} */
1999
    let bytes = [];
2000
    for (let i=0; i<subChunks.length; i++) {
2001
      if (format == 'INFO') {
2002
        bytes = bytes.concat(
2003
          pack(subChunks[i]['chunkId'], types.fourCC),
2004
          pack(subChunks[i]['value'].length + 1, uInt32_),
2005
          this.writeString_(
2006
            subChunks[i]['value'], subChunks[i]['value'].length));
2007
        bytes.push(0);
2008
      } else if (format == 'adtl') {
2009
        if (['labl', 'note'].indexOf(subChunks[i]['chunkId']) > -1) {
2010
          bytes = bytes.concat(
2011
            pack(subChunks[i]['chunkId'], types.fourCC),
2012
            pack(
2013
              subChunks[i]['value'].length + 4 + 1, uInt32_),
2014
            pack(subChunks[i]['dwName'], uInt32_),
2015
            this.writeString_(
2016
              subChunks[i]['value'],
2017
              subChunks[i]['value'].length));
2018
          bytes.push(0);
2019
        } else if (subChunks[i]['chunkId'] == 'ltxt') {
2020
          bytes = bytes.concat(
2021
            this.getLtxtChunkBytes_(subChunks[i]));
2022
        }
2023
      }
2024
      if (bytes.length % 2) {
2025
        bytes.push(0);
2026
      }
2027
    }
2028
    return bytes;
2029
  }
2030
2031
  /**
2032
   * Return the bytes of a 'ltxt' chunk.
2033
   * @param {!Object} ltxt the 'ltxt' chunk.
2034
   * @return {!Array<number>} The 'ltxt' chunk bytes.
2035
   * @private
2036
   */
2037
  getLtxtChunkBytes_(ltxt) {
2038
    return [].concat(
2039
      pack(ltxt['chunkId'], types.fourCC),
2040
      pack(ltxt['value'].length + 20, uInt32_),
2041
      pack(ltxt['dwName'], uInt32_),
2042
      pack(ltxt['dwSampleLength'], uInt32_),
2043
      pack(ltxt['dwPurposeID'], uInt32_),
2044
      pack(ltxt['dwCountry'], uInt16_),
2045
      pack(ltxt['dwLanguage'], uInt16_),
2046
      pack(ltxt['dwLanguage'], uInt16_),
2047
      pack(ltxt['dwCodePage'], uInt16_),
2048
      this.writeString_(ltxt['value'], ltxt['value'].length));
2049
  }
2050
2051
  /**
2052
   * Return the bytes of the 'junk' chunk.
2053
   * @return {!Array<number>} The 'junk' chunk bytes.
2054
   * @private
2055
   */
2056
  getJunkBytes_() {
2057
    /** @type {!Array<number>} */
2058
    let bytes = [];
2059
    if (this.junk.chunkId) {
2060
      return bytes.concat(
2061
        pack(this.junk.chunkId, types.fourCC),
2062
        pack(this.junk.chunkData.length, uInt32_),
2063
        this.junk.chunkData);
2064
    }
2065
    return bytes;
2066
  }
2067
2068
  /**
2069
   * Return 'RIFF' if the container is 'RF64', the current container name
2070
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
2071
   * @return {string}
2072
   * @private
2073
   */
2074
  correctContainer_() {
2075
    return this.container == 'RF64' ? 'RIFF' : this.container;
2076
  }
2077
2078
  /**
2079
   * Set the string code of the bit depth based on the 'fmt ' chunk.
2080
   * @private
2081
   */
2082
  bitDepthFromFmt_() {
2083
    if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
2084
      this.bitDepth = '32f';
2085
    } else if (this.fmt.audioFormat == 6) {
2086
      this.bitDepth = '8a';
2087
    } else if (this.fmt.audioFormat == 7) {
2088
      this.bitDepth = '8m';
2089
    } else {
2090
      this.bitDepth = this.fmt.bitsPerSample.toString();
2091
    }
2092
  }
2093
2094
  /**
2095
   * Return a .wav file byte buffer with the data from the WaveFile object.
2096
   * The return value of this method can be written straight to disk.
2097
   * @return {!Uint8Array} The wav file bytes.
2098
   * @private
2099
   */
2100
  createWaveFile_() {
2101
    /** @type {!Array<number>} */
2102
    let samplesBytes = this.samplesToBytes_();
2103
    /** @type {!Array<number>} */
2104
    let fileBody = [].concat(
2105
      pack(this.format, types.fourCC),
2106
      this.getJunkBytes_(),
2107
      this.getDs64Bytes_(),
2108
      this.getBextBytes_(),
2109
      this.getFmtBytes_(),
2110
      this.getFactBytes_(),
2111
      pack(this.data.chunkId, types.fourCC),
2112
      pack(samplesBytes.length, uInt32_),
2113
      samplesBytes,
2114
      this.getCueBytes_(),
2115
      this.getSmplBytes_(),
2116
      this.getLISTBytes_());
2117
    return new Uint8Array([].concat(
2118
      pack(this.container, types.fourCC),
2119
      pack(fileBody.length, uInt32_),
2120
      fileBody));      
2121
  }
2122
}
2123
2124
this.WaveFile = WaveFile;
2125